﻿using CleanArchitecture.Core.Configuration;
using CleanArchitecture.Core.Events;
using CleanArchitecture.Core.Helper;
using CleanArchitecture.Core.Interfaces;
using CleanArchitecture.Infrastructure.Entities;
using CleanArchitecture.Infrastructure.Entities.AdminUsers;
using CleanArchitecture.Infrastructure.Entities.Settings;
using CleanArchitecture.Infrastructure.Interfaces.AdminUsers;
using CleanArchitecture.Infrastructure.Interfaces.Security;
using System;

namespace CleanArchitecture.Infrastructure.Services.AdminUsers
{
    public partial class AdminUserRegistrationService : IAdminUserRegistrationService
    {
        #region Fields
        private readonly IAdminUserService _adminuserService;
        private readonly SecuritySettings _securitySettings;
        private readonly AdminUserSettings _adminuserSettings;
        private readonly IEncryptionService _encryptionService;
        private readonly IWebHelper _webHelper;
        private readonly IDomainEventDispatcher _eventDispatcher;
        #endregion

        #region Ctor
        public AdminUserRegistrationService(SecuritySettings securitySettings,
            AdminUserSettings adminuserSettings,
            IAdminUserService adminuserService,
            IEncryptionService encryptionService,
            IWebHelper webHelper,
            IDomainEventDispatcher eventDispatcher)
        {
            this._securitySettings = securitySettings;
            this._adminuserSettings = adminuserSettings;
            this._adminuserService = adminuserService;
            this._encryptionService = encryptionService;
            this._webHelper = webHelper;
            this._eventDispatcher = eventDispatcher;
        }
        #endregion

        #region Utilities

        /// <summary>
        /// Check whether the entered password matches with a saved one
        /// </summary>
        /// <param name="DbPassword">Customer password</param>
        /// <param name="enteredPassword">The entered password</param>
        /// <returns>True if passwords match; otherwise false</returns>
        protected bool PasswordsMatch(AdminUserPassword DbPassword, string enteredPassword)
        {
            if (DbPassword == null)  
                return false;
            if(string.IsNullOrEmpty(DbPassword.Password) || string.IsNullOrEmpty(enteredPassword))
                return false;

            var savedPassword = string.Empty;
            switch (DbPassword.PasswordFormat)
            {
                case EntityEnumConfig.PasswordFormat.Clear:
                    savedPassword = enteredPassword;
                    break;
                case EntityEnumConfig.PasswordFormat.Encrypted:
                    savedPassword = _encryptionService.EncryptText(enteredPassword);
                    break;
                case EntityEnumConfig.PasswordFormat.Hashed:
                    savedPassword = _encryptionService.CreatePasswordHash(enteredPassword, DbPassword.PasswordSalt, _adminuserSettings.HashedPasswordFormat);
                    break;
                default: //None
                    savedPassword = enteredPassword;
                    break;
            }

            return DbPassword.Password.Equals(savedPassword);
        }

        #endregion

        #region Methods
        /// <summary>
        /// Validate AdminUser
        /// </summary>
        /// <param name="username">Username or email</param>
        /// <param name="password">Password</param>
        /// <returns>Result</returns>
        public AdminUser ValidateAdminUser(string username, string password)
        {
            AdminUser user = null;
            bool isLoginname = false;
            if (_securitySettings.AdminUserLogin != null && _securitySettings.AdminUserLogin?.Count > 0)
            {
                if (_securitySettings.AdminUserLogin.Contains(AdminUserLoginType.Email.ToString("F")) && CommonHelper.IsValidEmail(username))
                    user = _adminuserService.GetAdminUserByEmail(username);
                else if (_securitySettings.AdminUserLogin.Contains(AdminUserLoginType.Mobile.ToString("F")) && CommonHelper.IsValidMobile(username))
                    user = _adminuserService.GetAdminUserByMobile(username);
                else isLoginname = true;
            }
            else isLoginname = true;
            //else //默认方式（登录用户名）登录
            if(isLoginname) 
                user = _adminuserService.GetAdminUserByLoginname(username);

            if (user == null)
            {
                user = new AdminUser()
                {
                    ExtraInfo = LoginResults.UserNotExist
                };
                return user;
            }
            if (user.IsDeleted == EntityEnumConfig.DeleteState.Deleted)
            {
                user.ExtraInfo = LoginResults.Deleted;
                return user;
            }
            if (user.IsActive == EntityEnumConfig.LockState.InActive)
            {
                user.ExtraInfo = LoginResults.NotActive;
                return user;
            }
            var record = _adminuserService.GetCurrentPassword(user.Id);
            if(record == null)
            {
                user.ExtraInfo = LoginResults.NotRegistered;
                return user;
            }
            if (!PasswordsMatch(record, password))
            {
                //wrong password
                user.FailedLoginAttempts++;
                int allowedAttemps = _adminuserSettings.FailedPasswordAllowedAttempts > 0 
                                   ? _adminuserSettings.FailedPasswordAllowedAttempts 
                                   : 3;
                if (user.FailedLoginAttempts >= allowedAttemps)
                {
                    //lock out
                    user.IsActive = EntityEnumConfig.LockState.InActive;
                    //reset the counter
                    user.FailedLoginAttempts = 0;
                    user.SysLogLvl = SystemLogLevel.Information;
                    user.SysLogMsg = string.Format("后台用户：Id: {0}, 登录名: {1}, 输入密码错误次数超过系统设定尝试次数{2}次，已被锁定。",
                                                user.Id, username, allowedAttemps);
                }
                _adminuserService.UpdateAdminUser(user);
                user.ExtraInfo = LoginResults.WrongPassword;

                return user;
            }

            //update login details
            user.FailedLoginAttempts = 0;
            user.LastLoginTimeUtc = System.DateTime.UtcNow;
            user.LastLoginIp = _webHelper.GetCurrentIpAddress();
            user.ExtraInfo = LoginResults.Successful;
            _adminuserService.UpdateAdminUser(user);

            return user;
        }
        /// <summary>
        /// Change password
        /// </summary>
        /// <param name="request">Request</param>
        /// <returns>Result</returns>
        public virtual bool ChangePassword(int AdminUserId, int PwdStrong, string OldPassword, string NewPassword, EntityEnumConfig.PasswordFormat Format = EntityEnumConfig.PasswordFormat.None, bool SkipOldPasswordCheck = false)
        {
            if (string.IsNullOrWhiteSpace(NewPassword) || (string.IsNullOrEmpty(OldPassword) && !SkipOldPasswordCheck))
                throw new ArgumentNullException(nameof(NewPassword) + " Or " + nameof(OldPassword));
            if (NewPassword.Length < _adminuserSettings.PasswordMinLength || NewPassword.Length > _adminuserSettings.PasswordMaxLength)
                return false;

            AdminUserPassword userCurrentPassword = _adminuserService.GetCurrentPassword(AdminUserId);
            if (userCurrentPassword == null && SkipOldPasswordCheck) //新用户注册
            {
                userCurrentPassword = new AdminUserPassword();
                userCurrentPassword.FkUserId = AdminUserId;
            }
            if (!SkipOldPasswordCheck)
            {
                if (!PasswordsMatch(userCurrentPassword, OldPassword))
                    return false;
            }
            
            var pwdFormat = Format != EntityEnumConfig.PasswordFormat.None
                   ? Format
                   : CommonHelper.To<EntityEnumConfig.PasswordFormat>(_adminuserSettings.DefaultPasswordFormat);
            switch (pwdFormat)
            {
                case EntityEnumConfig.PasswordFormat.None:
                case EntityEnumConfig.PasswordFormat.Clear:
                    userCurrentPassword.PasswordFormatId = (int)EntityEnumConfig.PasswordFormat.Clear;
                    userCurrentPassword.Password = NewPassword;
                    break;
                case EntityEnumConfig.PasswordFormat.Encrypted:
                    userCurrentPassword.PasswordFormatId = (int)EntityEnumConfig.PasswordFormat.Encrypted;
                    userCurrentPassword.Password = _encryptionService.EncryptText(NewPassword);
                    break;
                case EntityEnumConfig.PasswordFormat.Hashed:
                    userCurrentPassword.PasswordFormatId = (int)EntityEnumConfig.PasswordFormat.Hashed;
                    int saltKeySize = _adminuserSettings.PasswordMinLength.HasValue
                                    ? _adminuserSettings.PasswordMinLength.Value
                                    : 5;
                    var saltKey = _encryptionService.CreateSaltKey(saltKeySize);
                    userCurrentPassword.PasswordSalt = saltKey;
                    userCurrentPassword.Password = _encryptionService.CreatePasswordHash(NewPassword, saltKey, _adminuserSettings.HashedPasswordFormat);
                    break;
            }
            userCurrentPassword.PasswordStrongLevel = (EntityEnumConfig.PasswordStrongLevel)Enum.ToObject(typeof(EntityEnumConfig.PasswordStrongLevel), PwdStrong); ;
            userCurrentPassword.CreatedOnTimeUtc = DateTime.UtcNow;
            _adminuserService.UpdateAdminUserPassword(userCurrentPassword);
            //publish event
            _eventDispatcher.Dispatch(new PasswordChangedEvent<AdminUserPassword>(userCurrentPassword));
            return true;
        }
        #endregion
    }
}
